// 四.手写实现MyPromis编码
// 要求：尽可能还原Promise中的每一个API，并通过注释的方式描述思路和原理。

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(exefn) {
        try {
            exefn(this.resolve, this.reject)
        } catch (error) {
            this.reject(error)
        }
    }

    status = PENDING

    value = undefined
    reason = undefined

    successCallback = []
    failCallback = []

    resolve = value => {
        // status 不是PENDING，则不再执行
        if (this.status !== PENDING) return;

        // 改变状态为 fulfilled
        this.status = FULFILLED;

        // 保存执行器成功时传入的值
        this.value = value;

        // 兼顾同步。所以先判断 successCallback 是否存在，存在则执行
        // this.successCallback && this.successCallback(this.value)

        // 执行其中有异步时，依次执行所有 then 的成功回调
        while (this.successCallback.length) {
            // shift 会取得数组中的第一个元素并返回，且改变原数组
            // 但 this.successCallback.length 为 0 时，结束 while 循环
            this.successCallback.shift()()
        }
    }
    reject = reason => {
        // status 不是PENDING，则不再执行
        if (this.status !== PENDING) return;

        // 改变状态为 rejected
        this.status = REJECTED;

        // 保存执行器失败时传入的错误原因
        this.reason = reason;

        // 兼顾同步。所以先判断 failCallback 是否存在，存在则执行
        // this.failCallback && this.failCallback(this.reason)

        // 执行其中有异步时，依次执行所有 then 的失败回调
        while (this.failCallback.length) {
            // shift 会取得数组中的第一个元素并返回，且改变原数组
            // 但 this.failCallback.length 为 0 时，结束 while 循环
            this.failCallback.shift()()
        }
    }

    then(successCallback, failCallback) {
        // 处理 then 的参数为可选的情况
        // 这里的 value、reason 为形参
        successCallback = successCallback ? successCallback : value => value;
        failCallback = failCallback ? failCallback : reason => { throw reason };

        let returnPromise = new MyPromise((resolve, reject) => {

            // 判断 status 状态，来决定调用的回调
            if (this.status === FULFILLED) {
                /**
                 * 直接在 judgeCallbackReturn 中传递 returnPromise 是有问题的
                 * returnPromise 需要在 new MyPromise() 完成后在有值，所以这里使用 setTimeout 异步一下
                 */
                setTimeout(() => {
                    try {
                        let successReturn = successCallback(this.value);
                        judgeCallbackReturn(returnPromise, successReturn, resolve, reject);
                    } catch (error) {
                        reject(error)
                    }
                }, 0);

            } else if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let failReturn = failCallback(this.reason);
                        judgeCallbackReturn(returnPromise, failReturn, resolve, reject);
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            } else {
                // 执行器中有异步，status 仍为 pending 时
                // 对于同一 MyPromise 实例多次使用 then，在 pending 时，将回调存入相应的暂存回调数组中，以保证每一个回调都会在后续中执行

                // this.successCallback.push(successCallback);
                // this.failCallback.push(failCallback);

                // 因为 then 的链式调用需要用 resolve，所以暂存回调数组中的回调函数改成一下形式
                this.successCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let successReturn = successCallback(this.value);
                            judgeCallbackReturn(returnPromise, successReturn, resolve, reject);
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                });
                this.failCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let failReturn = failCallback(this.reason);
                            judgeCallbackReturn(returnPromise, failReturn, resolve, reject);
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                });
            }
        })

        return returnPromise;
    }

    finally(callback) {
        // 使用 then 得到当前 MyPromise 的状态
        // 在 then 的 resolve/reject 中调用 callback 则无论 MyPromise 是否成功, 都会执行
        // then 会返回一个 MyPromise 对象，所以 return 后，finally 后面就可以再接 then
        return this.then(res => {
            // 在 finally 后调用then，该 then 会拿到 finally 返回的 MyPromise 的结果，所以这里要 return res
            // callback();
            // return res

            // 若 callback 有异步，则用 MyPromise.resolve() 把 callback 变成 MyPromise，
            // 然后在其后面调用 then，并在其成功回调中返回 res
            // 这样就可以确保 callback 执行完毕后再返回当前 MyPromise 的结果
            return MyPromise.resolve(callback()).then(() => res)
        }, err => {
            // 失败时，需要 throw err，如此才能将错误信息传递给 finally 后面调用的 then
            // callback();
            // throw err

            // 用 MyPromise.resolve() 把 callback 变成 MyPromise，调用 then，并在其成功回调中 throw err 来传递错误
            return MyPromise.resolve(callback()).then(() => {throw err})
        })
    }

    catch(failCallback) {
        // 使用 then 得到当前 MyPromise 的状态，若失败，则执行 catch 的回调
        // 使用 return 返回 MyPromise 对象，以便 catch 后面链式调用 MyPromise 其他方法
        return this.then(undefined, failCallback)
    }

    static all(array) {
        let results = []; // 存储 array 中每项的结果
        let index = 0; // 记数处理完成的个数

        // 返回 MyPromise 对象
        return new MyPromise((resolve, reject) => {
            function addResult(key,value) {
                results[key] = value;
                index++;

                if(index === array.length) {
                    resolve(results)
                }
            }

            // 循环获取 array 中的每一项
            for (let i = 0; i < array.length; i++) {
                let current = array[i];
                // 判断 current 是 普通值 还是 MyPromise 对象
                if (current instanceof MyPromise) {
                    // 因为 current 作为 MyPromise 对象，其中可能存在异步的情况
                    // 而 for 循环瞬间完成，current.then 的回调还来不及执行，这会造成 result 中有空值

                    // 为解决异步问题，这里在 all 中添加一个 index 变量作为记数，定义 addResult(key,value) 函数作为将结果保存进 results 的操作
                    // addResult 没执行一次，index 加一， 当 index 等于 array.length 时，代表所有项已经执行完成，此时调用 resolve
                    current.then(res => { addResult(i, res) }, reject)
                } else {
                    addResult(i, current)
                }
            }
        })
    }

    static resolve(value) {
        // 判断是否为 MyPromise 对象
        // 是，则直接返回
        if(value instanceof MyPromise) return value;

        // 否，返回一个成功的 MyPromise
        return new MyPromise(resolve => resolve(value))
    }
}

/**
 * 新建函数来统一处理对返回值的判断
 *
 * 1、判断 returnValue 是否为 returnPromise，是则直接抛出异常
 * 2、判断 returnValue 是否为 MyPromise 对象
 *    是，则使用 then 判断 returnValue 的状态，回调为 returnPromise 的 resolve, reject
 *    否，则使用 resolve
 */
function judgeCallbackReturn(returnPromise, returnValue, resolve, reject) {
    if (returnValue === returnPromise) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    if (returnValue instanceof MyPromise) {
        returnValue.then(resolve, reject)
    } else {
        resolve(returnValue)
    }

}

module.exports = MyPromise